package xj.graph2d;

import java.awt.Adjustable;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.util.Observable;
import java.util.Observer;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JPanel;

import xj.app.DrawTool;
import xj.app.DrawToolkit;
import xj.util.gui.JPanelPopup;
import xj.util.gui.Rectangle2DFloat;

public class View 
  extends JPanel 
  implements Observer, Printable {

  public View(Document doc) {
    this(doc, Document.DEFAULT_CANVAS_WIDTH, 
	 Document.DEFAULT_CANVAS_HEIGHT, true);
  }

  public View(Document doc, 
	      int canvasWidth, 
	      int canvasHeight) {
    this(doc, canvasWidth, canvasHeight, true);
  }

  protected View(Document doc, 
		 int canvasWidth, 
		 int canvasHeight, 
		 boolean scroll) {
    if (doc != null) {
      doc.addObserver(this);
    }
    document = doc;
    canvas = new DrawCanvas(canvasWidth, canvasHeight, this);
    setLayout(new BorderLayout());
    if (scroll) {
      scrollcanvas = new ScrollCanvas(ScrollCanvas.SCROLLBARS_ALWAYS, canvas);
      setBorder(BorderFactory.createEtchedBorder());
      add(scrollcanvas, BorderLayout.CENTER);
    } else {
      add(canvas, BorderLayout.CENTER);
    }
    initView();
  }

  public boolean isStencilMode() { 
    return stencilMode;
  }

  public void setStencilMode(boolean m) { 
    stencilMode = m;
  }

  public void deleteView() {
    if (document != null) {
      document.deleteObserver(this);
    }
  }

  public void setDrawToolkit(DrawToolkit toolkit) {
    this.toolkit = toolkit;
  }

  public DrawToolkit getDrawToolkit() {
    return toolkit;
  }

  public void addTool(DrawTool tool) {
    addTool(tool, false);
  }

  public void addTool(DrawTool tool, boolean current) {
    int i = toolkit.addTool(tool);
    if (current || toolkit.getToolCount() == 1) {
      toolkit.setCurrentTool(i, this);
    }
  }

  public void addTool(String name, DrawTool tool) {
    addTool(name, tool, false);
  }

  public void addTool(String name, DrawTool tool, boolean current) {
    int i = toolkit.addTool(name, tool);
    if (current || toolkit.getToolCount() == 1) {
      toolkit.setCurrentTool(i, this);
    }
  }

  public DrawTool getSpecialTool() {
    return specialTool;
  }

  public void setSpecialTool(DrawTool tool) {
    this.specialTool = tool;
  }

  public void setCurrentTool(String name) {
    toolkit.setCurrentTool(name, this);
  }

  public void setCurrentTool(int t) {
    toolkit.setCurrentTool(t, this);
  }

  public void setCurrentTool(DrawTool t) {
    toolkit.setCurrentTool(t, this);
  }

  public DrawTool getCurrentTool() {
    if (specialTool != null) {
      return specialTool;
    } else if (toolkit != null) {
      return toolkit.getCurrentTool();
    }
    return null;
  }

  public DrawTool getLastTool() {
    return toolkit.getLastTool();
  }

  public DrawTool getTool(int t) {
    return toolkit.getTool(t);
  }

  public DrawTool getTool(String name) {
    return toolkit.getTool(name);
  }

  public Document getDocument() {
    return document;
  }

  public DrawCanvas getCanvas() {
    return canvas;
  }

  public ScrollCanvas getScrollCanvas() {
    return scrollcanvas;
  }

  public void moveScrollPosition(int dx, int dy) {
    if (scrollcanvas != null) {
      Adjustable vadj = scrollcanvas.getVAdjustable();
      Adjustable hadj = scrollcanvas.getHAdjustable();
      if (vadj != null && hadj != null) {
	int vval = vadj.getValue() + dy;
	int hval = hadj.getValue() + dx;
	int vmin = vadj.getMinimum();
	int vmax = vadj.getMaximum();
	int hmin = hadj.getMinimum();
	int hmax = hadj.getMaximum();
	if (vval < vmin) {
	  vval = vmin;
	}
	if (vval > vmax) {
	  vval = vmax;
	}
	if (hval < hmin) {
	  hval = hmin;
	}
	if (hval > hmax) {
	  hval = hmax;
	}
	vadj.setValue(vval);
	hadj.setValue(hval);
      }
    }
  }

  public Color getBackgroundColor() {
    return bgcolor;
  }

  public void setBackgroundColor(Color color) {
    bgcolor = color;
  }

  public Image getBackgroundImage() {
    return backgroundImage;
  }

  public void setBackgroundImage(Image image) {
    backgroundImage = image;
  }

  public Color getThumbNailBackgroundColor() {
    return thumbnailBgcolor;
  }

  public void setThumbNailBackgroundColor(Color color) {
    thumbnailBgcolor = color;
  }

  public void activateView() {
    //System.out.println("View.activateView()");
    if (canvas != null) {
      canvas.repaint();
      if (toolkit.getToolCount() > 0) { 
	//&& toolkit.getCurrentTool() == null) {
	toolkit.setCurrentTool(0, this);
      }
    }
  }

  /*
  public Point getMousePosition() { 
    PointerInfo pi = MouseInfo.getPointerInfo();
    Point loc = pi.getLocation();
    Component root = SwingUtilities.getRoot(this);
    Point pr = root.getLocationOnScreen(); 
    Point p0 = new Point(loc.x - pr.x, loc.y - pr.y);
    return SwingUtilities.convertPoint(root, p0, this);
  }
  */

  public void deactivateView() {
  }

  public void update(Observable o, Object arg) {
    if (arg != null && arg.equals("canvas dimension")) {
      setVirtualCanvasRectangle(new Rectangle2DFloat(0, 0, document.getCanvasWidth(), 
						     document.getCanvasHeight()));
    }
    canvas.repaint();
    if (thumbnail != null) {
      thumbnail.repaint();
    }
    
  }

  public void refresh() {
    canvas.repaint();
    if (thumbnail != null) {
      thumbnail.repaint();
    }
  }

  public void paintCanvas(Graphics2D g2, 
			  Rectangle2D rect, 
			  boolean printing,
			  boolean thumbnail) {
  }

  public void paintCanvas(Graphics2D g2, 
			  Rectangle2D rect, 
			  boolean printing) {
    paintCanvas(g2, rect, printing, false);
  }

  public void paintCanvas(Graphics2D g2, Rectangle2D rect) {
    paintCanvas(g2, rect, false, false);
  }

  public void paintCanvas(Graphics2D g2, boolean printing, boolean thumbnail) {
    paintCanvas(g2, getViewportRectangle(), printing, thumbnail);
  }

  public void paintCanvas(Graphics2D g2) {
    paintCanvas(g2, getViewportRectangle(), false, false);
  }

  static protected final Stroke dashedGridStroke = 
    new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f,
		    new float[] { 6, 6 }, 0);

  public void paintBackgroundAndGrid(Graphics2D g2, Rectangle2D rect) {
    int xMin = 0, xMax = 0, yMin = 0, yMax = 0;
    Rectangle2D canvasRect = getVirtualCanvasRectangle();

    // draw background
    if (rect == null) {
      rect = canvasRect;
      xMin = (int) rect.getX();
      yMin = (int) rect.getY();
      xMax = xMin + (int) rect.getWidth();
      yMax = yMin + (int) rect.getHeight();
    } else {
      xMin = (int) Math.max(rect.getX(), canvasRect.getX());
      yMin = (int) Math.max(rect.getY(), canvasRect.getY());
      xMax = (int) Math.min(rect.getX() + rect.getWidth(), 
			    canvasRect.getX() + canvasRect.getWidth());
      yMax = (int) Math.min(rect.getY() + rect.getHeight(), 
			    canvasRect.getY() + canvasRect.getHeight());
    }

    if (backgroundImage != null) {
      int w = backgroundImage.getWidth(this);
      int h = backgroundImage.getHeight(this);
      Shape clip = g2.getClip();
      g2.setClip(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1);
      for (int x = 0; x < xMax; x += w) {
	if (x + w > xMin) {
	  for (int y = 0; y < yMax; y += h) {
	    if (y + h > yMin) {
	      g2.drawImage(backgroundImage, x, y, this);
	    }
	  }
	}
      }
      g2.setClip(clip);
    } else {
      g2.setColor(bgcolor);
      g2.fillRect(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1);
    }

    // draw grid
    if (gridOn) {
      Color gcolor = getGridColor();
      if (gcolor != null) {
	g2.setColor(gcolor);
      }
      int x0 = (xMin / gridGap) * gridGap;
      int y0 = (yMin / gridGap) * gridGap;
      int x, y;
      if (gridType == GRID_DOT) {
	for (x = x0; x <= xMax; x += gridGap) {
	  for (y = y0; y <= yMax; y += gridGap) {
	    g2.drawLine(x, y, x, y);
	  }
	}
      } else if (gridType == GRID_SOLID_LINE) {
	for (x = x0; x <= xMax; x += gridGap) {
	  g2.drawLine(x, yMin, x, yMax);
	}
	for (y = y0; y <= yMax; y += gridGap) {
	  g2.drawLine(xMin, y, xMax, y);
	}
      } else if (gridType == GRID_DASHED_LINE) {
	Stroke s = g2.getStroke();
	g2.setStroke(dashedGridStroke);
	for (x = x0; x <= xMax; x += gridGap) {
	  g2.draw(new Line2D.Float(x, yMin, x, yMax));
	}
	for (y = y0; y <= yMax; y += gridGap) {
	  g2.draw(new Line2D.Float(xMin, y, xMax, y));
	}
	g2.setStroke(s);
      }

      // draw the boundary of the canvas
      g2.setColor(Color.black);
      g2.draw(canvasRect);
    }
  }

  public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
      throws PrinterException {
    return NO_SUCH_PAGE;
  }

  public boolean onCommand(String cmd) {
    return false;
  }

  public void setSelected() {
  }

  public void setClosed() {
  }

  public void displayLocation(float x, float y) {
  }

  public void displayLocation(int x, int y) {
  }

  public void displayMessage(String text) {
  }

  public void displayHelpMessage(String key) {
  }

  public void startBusyMessage(String key) {
    displayHelpMessage(key);
  }

  public void endBusyMessage(String key) {
    displayHelpMessage(key);
  }

  public Point virtualToPhysical(Point2D p) {
    if (canvas != null) {
      return canvas.virtualToPhysical(p);
    } else {
      return null;
    }
  }

  public Point2D physicalToVirtual(Point p) {
    if (canvas != null) {
      return canvas.physicalToVirtual(p);
    } else {
      return null;
    }
  }

  public Rectangle virtualToPhysical(Rectangle2D r) {
    if (canvas != null) {
      return canvas.virtualToPhysical(r);
    } else {
      return null;
    }
  }

  public Rectangle2D physicalToVirtual(Rectangle r) {
    if (canvas != null) {
      return canvas.physicalToVirtual(r);
    } else {
      return null;
    }
  }

  public Point2D getViewportLocation() {
    if (canvas != null) {
      return canvas.getViewportLocation();
    } else {
      return null;
    }
  }

  public void setViewportLocation(float x, float y) {
    if (canvas != null) {
      canvas.setViewportLocation(x, y, true);
    }
  }

  public Rectangle2D getViewportRectangle() {
    if (canvas != null) {
      return canvas.getViewportRectangle();
    } else {
      return null;
    }
  }

  public void setVirtualCanvasRectangle(Rectangle2D r) {
    if (canvas != null) {
      canvas.setVirtualCanvasRectangle(r);
      initThumbNail();
    }
  }

  public Rectangle2D getVirtualCanvasRectangle() {
    if (canvas != null) {
      return canvas.getVirtualCanvasRectangle();
    }
    return null;
  }

  public float getScaleFactor() {
    if (canvas != null) {
      return canvas.getScaleFactor();
    } else {
      return 1.0f;
    }
  }

  public void setScaleFactor(float f) {
    if (canvas != null) {
      canvas.setScaleFactor(f);
    }
  }

  public void setDefaultVirtualCanvasRectangle(Rectangle2D r) {
    if (r != null) {
      defaultVirtualCanvasRectangle = r;
    }
  }

  public Rectangle2D getDefaultVirtualCanvasRectangle() {
    return defaultVirtualCanvasRectangle;
  }

  public DrawCanvas getThumbNail() {
    return thumbnail;
  }

  public void setThumbNail(int thumbWidth, int thumbHeight) {
    setThumbNail(thumbWidth, thumbHeight, 0.125f);
  }

  public void setThumbNail(int thumbWidth, int thumbHeight, float factor) {
    thumbnail = new DrawCanvas(thumbWidth, thumbHeight, this, true, canvas);
    thumbnail.setScaleFactor(factor);
  }

  public DrawCanvas createThumbNail(int thumbWidth, int thumbHeight, float factor) {
    DrawCanvas t = new DrawCanvas(thumbWidth, thumbHeight, this, true, canvas);
    t.drawThumbViewport = false;
    t.setScaleFactor(factor);
    return t;
  }

  public DrawCanvas createThumbNail(float factor) {
    Rectangle2D rect = getVirtualCanvasRectangle();
    int w = (int) (rect.getWidth() * factor);
    int h = (int) (rect.getHeight() * factor);
    return createThumbNail(w, h, factor);
  }

  public DrawCanvas createThumbNail(int thumbWidth, int thumbHeight) {
    Rectangle2D rect = getVirtualCanvasRectangle();
    int rectWidth = (int) rect.getWidth();
    int rectHeight = (int) rect.getHeight();
    float factor = Math.min((float) thumbWidth / (float) rectWidth, 
			    (float) thumbHeight / (float) rectHeight);
    return createThumbNail(thumbWidth, thumbHeight, factor);
  }

  public boolean isGridOn() {
    return gridOn;
  }

  public void setGridOn(boolean gridOn) {
    this.gridOn = gridOn;
  }

  public int getGridSize() {
    return gridSize;
  }

  public void setGridSize(int gridSize) {
    this.gridSize = gridSize;
    if (GRID_LARGE == gridSize) {
      this.gridGap = GRID_LARGE_GAP;
    } else if (GRID_SMALL == gridSize) {
      this.gridGap = GRID_SMALL_GAP;
    }
  }

  public int getGridGap() {
    return gridGap;
  }

  public void setGridGap(int gridGap) {
    this.gridGap = gridGap;
  }

  public int getGridType() {
    return gridType;
  }

  public void setGridType(int gridType) {
    this.gridType = gridType;
  }

  public Color getGridColor() {
    if (stencilMode) { 
      return gridColorStencil;
    } else { 
      return gridColor;
    }
  }

  public boolean isShowMargin() {
    return scrollcanvas.isShowMargin();
  }

  public void setShowMargin(boolean showMargin) {
    scrollcanvas.setShowMargin(showMargin);
  }

  public boolean isShowRuler() {
    return scrollcanvas.isShowRuler();
  }

  public void setShowRuler(boolean showRuler) {
    scrollcanvas.setShowRuler(showRuler);
  }

  public boolean isUseAntialias() {
    return useAntialias;
  }

  public void setUseAntialias(boolean useAntialias) {
    this.useAntialias = useAntialias;
    repaint();
  }

  public boolean isFractionalMetrics() {
    return fractionalMetrics;
  }

  public void setFractionalMetrics(boolean fractionalMetrics) {
    this.fractionalMetrics = fractionalMetrics;
    repaint();
  }

  public float getMarginWidth() {
    return document.getMarginWidth();
  }

  public void setMarginWidth(float marginWidth) {
    document.setMarginWidth(marginWidth);
  }

  public float getMarginHeight() {
    return document.getMarginHeight();
  }

  public void setMarginHeight(float marginHeight) {
    document.setMarginHeight(marginHeight);
  }

  public void showPopup(JPanelPopup popup, int x, int y) {
    if (debug) { 
      System.out.println("View.showPopup()");
    }
    if (popup != null) { 
      if (isShowRuler()) { 
	x += ScrollCanvas.RULER_HEIGHT;
	y += ScrollCanvas.RULER_HEIGHT;
      }
      popup.setParentLocation(this, x, y);
      popup.showPopup(this);
      isPopupVisible = true;
    }
  }

  public void hidePopup(JPanelPopup popup) { 
    if (popup != null) { 
      popup.hidePopup();
    }
    isPopupVisible = false;
  }

  public void showThumbNail(JComponent thumbnail, Point p) {
    if (thumbnail != null) {
      if (popup == null) {
	popup = new JPanelPopup(false);
	//popup.setOpaque(false);
	popup.setBackground(new Color(0, 0, 0, 0));
      } else {
	popup.removeAll();
      }
      popup.setParentLocation(this, p.x, p.y);
      // thumbnail.setBorder(BorderFactory.createLineBorder(Color.black));
      popup.setBorder(BorderFactory.createLineBorder(Color.black));
      popup.addComponent(thumbnail, BorderLayout.CENTER);
      popup.pack();

      popup.showPopup(this);
    }
  }

  public void showThumbNail(JComponent thumbnail) {
    if (thumbnail != null) {
      if (popup == null) {
	popup = new JPanelPopup();
      } else {
	popup.removeAll();
      }
      // thumbnail.setBorder(BorderFactory.createLineBorder(Color.black));
      popup.setBorder(BorderFactory.createLineBorder(Color.black));
      popup.addComponent(thumbnail, BorderLayout.CENTER);
      popup.pack();

      Dimension dim = popup.getSize();
      Dimension cdim = canvas.getSize();

      if (debug) {
	System.out.println("View.showThumbNail(() dim=" + dim);
	System.out.println("View.showThumbNail(() cdim=" + cdim);
      }

      popup.setParentLocation(canvas, 0, cdim.height - dim.height);
      popup.showPopup(canvas);
    }
  }

  public void hideThumbNail() {
    if (popup != null) {
      popup.setBorder(null);
      popup.hidePopup();
    }
  }

  public static boolean isWaterMark() {
    return waterMark;
  }

  public static void setWaterMark(boolean wm) {
    waterMark = wm;
  }

  protected void initThumbNail() {
  }

  protected void initView() {
  }

  protected boolean stencilMode = false; 

  protected DrawCanvas canvas;

  protected DrawCanvas thumbnail;

  protected ScrollCanvas scrollcanvas;

  protected Color bgcolor = Color.white;

  protected Color thumbnailBgcolor = new Color(255, 255, 255, 128);

  // Color.white;

  protected Image backgroundImage = null;

  protected static boolean waterMark = true;

  public static final int GRID_DOT = 0;

  public static final int GRID_SOLID_LINE = 1;

  public static final int GRID_DASHED_LINE = 2;

  public static final int GRID_LARGE = 100;

  public static final int GRID_SMALL = 200;

  public static final int GRID_CUSTOM = 300;

  public static final int GRID_LARGE_GAP = 20;

  public static final int GRID_SMALL_GAP = 10;

  protected static final boolean debug = false;

  protected boolean gridOn = true;

  protected int gridSize = GRID_LARGE; // GRID_LARGE GRID_SMALL GRID_CUSTOM

  protected int gridGap = 20;

  protected int gridType = GRID_SOLID_LINE; // GRID_DOT, GRID_SOLID_LINE,

  // GRID_DASHED_LINE

  protected Color gridColor = new Color(0, 255, 255, 64);
  protected Color gridColorStencil = new Color(255, 0, 255, 64);

  protected Rectangle2D defaultVirtualCanvasRectangle = 
    new Rectangle2DFloat(0.0f, 0.0f, 1000.0f, 1000.0f);

  protected boolean useAntialias = true;

  protected boolean fractionalMetrics = true;

  protected Document document;

  protected DrawToolkit toolkit = null; // new DrawToolkit(512);

  protected DrawTool specialTool = null;

  protected JPanelPopup popup = new JPanelPopup(); // this is the thumbnail 

  protected boolean isPopupVisible = false; 

  protected void setDocument(Document doc) {
    if (document != null) {
      document.deleteObserver(this);
    }
    document = doc;
    if (doc != null) {
      doc.addObserver(this);
    }
    refresh();
  }

}
